﻿using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Linq;
using Ipmce.RemoteVoting.Common.Tools.Wcf.Unity.LifetimeManagers;
using Microsoft.Practices.Unity;

namespace Ipmce.RemoteVoting.Common.Tools.Wcf.Unity
{
	/// <summary>
	/// Configures the instance provider to use the <see cref="UnityInstanceProvider"/> for service creation.
	/// </summary>
	public class UnityServiceBehavior : IServiceBehavior
	{
		private readonly IUnityContainer _unityContainer;

		/// <summary>
		/// 
		/// </summary>
		/// <param name="unityContainer"></param>
		public UnityServiceBehavior(IUnityContainer unityContainer)
		{
			_unityContainer = unityContainer;
		}

		/// <summary>
		/// Gets or sets a value indicating whether <see cref="System.ServiceModel.OperationContext"/> support is enabled.
		/// </summary>
		public bool OperationContextEnabled
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets a value indicating whether <see cref="System.ServiceModel.InstanceContext"/> support is enabled.
		/// </summary>
		public bool InstanceContextEnabled
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets a value indicating whether <see cref="System.ServiceModel.ServiceHostBase"/> support is enabled.
		/// </summary>
		public bool ServiceHostBaseEnabled
		{
			get;
			set;
		}

		/// <summary>
		/// Gets or sets a value indicating whether <see cref="System.ServiceModel.IContextChannel"/> support is enabled.
		/// </summary>
		public bool ContextChannelEnabled
		{
			get;
			set;
		}

		#region IServiceBehavior Members

		/// <summary>
		/// Provides the ability to pass custom data to binding elements to support the contract implementation.
		/// </summary>
		/// <param name="serviceDescription">The service description of the service.</param>
		/// <param name="serviceHostBase">The host of the service.</param>
		/// <param name="endpoints">The service endpoints.</param>
		/// <param name="bindingParameters">Custom objects to which binding elements have access.</param>
		/// <remarks>Not used in this behavior.</remarks>
		public void AddBindingParameters(
			ServiceDescription serviceDescription,
			ServiceHostBase serviceHostBase,
			Collection<ServiceEndpoint> endpoints,
			BindingParameterCollection bindingParameters)
		{
		}

		/// <summary>
		/// Provides the ability to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects.
		/// </summary>
		/// <param name="serviceDescription">The service description.</param>
		/// <param name="serviceHostBase">The host that is currently being built.</param>
		/// <remarks>Updates the endpoints instance providers to use the <see cref="UnityInstanceProvider"/>.</remarks>
		public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
		{
			if (ServiceHostBaseEnabled)
			{
				serviceHostBase.Extensions.Add(new UnityServiceHostBaseExtension());
			}

			foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
			{
				var channelDispatcher = channelDispatcherBase as ChannelDispatcher;
				if (channelDispatcher != null)
				{
					foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
					{
						var interfaceType = GetContractInterfaceType(serviceDescription, endpointDispatcher);

						endpointDispatcher.DispatchRuntime.InstanceProvider =
							new UnityInstanceProvider(serviceDescription.ServiceType, _unityContainer, interfaceType);

						if (OperationContextEnabled)
						{
							endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new UnityOperationContextMessageInspector());
						}

						if (InstanceContextEnabled)
						{
							endpointDispatcher.DispatchRuntime.InstanceContextInitializers.Add(new UnityInstanceContextInitializer());
						}

						if (ContextChannelEnabled)
						{
							foreach (DispatchOperation operation in endpointDispatcher.DispatchRuntime.Operations)
							{
								operation.CallContextInitializers.Add(new UnityCallContextInitializer());
							}
						}
					}
				}
			}
		}

		private static Type GetContractInterfaceType(ServiceDescription serviceDescription, EndpointDispatcher endpointDispatcher)
		{
			var contractEndpoint = serviceDescription.Endpoints.Where(endpoint =>
				string.CompareOrdinal(endpointDispatcher.ContractName, endpoint.Contract.Name) == 0
					&& string.CompareOrdinal(endpointDispatcher.ContractNamespace, endpoint.Contract.Namespace) == 0)
					.FirstOrDefault();
			if (contractEndpoint == null)
				return null;
			return contractEndpoint.Contract.ContractType;
		}

		/// <summary>
		/// Provides the ability to inspect the service host and the service description to confirm that the service can run successfully.
		/// </summary>
		/// <param name="serviceDescription">The service description.</param>
		/// <param name="serviceHostBase">The service host that is currently being constructed.</param>
		/// <remarks>Not used in this behavior.</remarks>
		public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
		{
		}

		#endregion
	}
}